/*
 * cron.c
 *
 *  Created on: 2023年11月12日
 *      Author: hello
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#include "cron.h"

enum
{
	CRON_FIELD_TYPE_SECOND = 1,
	CRON_FIELD_TYPE_MINUTE,
	CRON_FIELD_TYPE_HOUR,
	CRON_FIELD_TYPE_DAY,
	CRON_FIELD_TYPE_MONTH,
	CRON_FIELD_TYPE_WEEKDAY,
};

static bool cron_prase_field(const char* field, uint8_t* bitmap, int fieldtype)
{
	static const int modulos[] = {0, 60, 60, 24, 31, 12, 7};
	int modulo = -1;
	int tmp = 0, failsafe = 0, begin = -1, last = -1, step = 0;
	char* p = NULL;

	if(fieldtype < CRON_FIELD_TYPE_SECOND || fieldtype > CRON_FIELD_TYPE_WEEKDAY)
		return false;

	modulo = modulos[fieldtype];

	while (1)
	{
		/* Handle numeric digit or symbol or '*' */
		if (*field == '*')
		{
			begin = 0;  /* everything will be filled */
			last  = (modulo - 1);
			step  = 1;
			field++;
		}
		else if(*field == '?')
		{
			return true;
		}
		else if (*field >= '0' && *field <= '9')
		{
			if (begin < 0)
			{
				begin = strtol(field, &p, 10);
				if(fieldtype == CRON_FIELD_TYPE_MONTH || fieldtype == CRON_FIELD_TYPE_DAY)
					begin -= 1;
				if(begin < 0 || begin >= modulo)
					return false;
			}
			else
			{
				last = strtol(field, &p, 10);
				if(fieldtype == CRON_FIELD_TYPE_MONTH || fieldtype == CRON_FIELD_TYPE_DAY)
					last -= 1;
				if(last < 0 || last >= modulo || last < begin)
					return false;
			}
			field = p;
			step = 1;
		}
		/* handle optional range '-' */
		if (step == 0)
			return false;
		if (*field == '-' && last < 0)
		{
			field++;
			continue;
		}
		/*
		 * collapse single-value ranges, handle skipmark, and fill
		 * in the character array appropriately.
		 */
		if (last < 0)
			last = begin;
		if (*field == '/')
		{
			step = strtol(field + 1, &p, 10);
			field = p;
			last = (modulo - 1);
		}
		/*
		 * fill array, using a failsafe is the easiest way to prevent
		 * an endless loop
		 */
		tmp = 1;
		failsafe = (modulo + 1);
		begin -= 1;
		do
		{
			begin = (begin + 1) % modulo;
			if (--tmp == 0)
			{
				bitmap[begin % modulo] = 1;
				tmp = step;
			}
			if (--failsafe == 0)
				return false;
		} while (begin != last);

		if (*field != ',')
			break;

		field++;
		begin = -1;
		last = -1;
	}

	return true;
}

typedef struct
{
	uint8_t second[60];   /* 0~59 */
	uint8_t minute[60];   /* 0~59 */
	uint8_t hour[24];     /* 0~23 */
	uint8_t day[31];      /* 0~30 */
	uint8_t month[12];    /* 0~11 */
	uint8_t weekday[7];   /* 0~6, beginning sunday */
} cron_bitmap_t;

static int cron_parse(const char* expression, cron_bitmap_t* bitmap)
{
	int fieldtype = CRON_FIELD_TYPE_SECOND;
	int index = 0;
	int weekused = 0, daysused = 0;
	uint8_t *bitmapfields[7] = {0};
	char field[256];

	memset(bitmap, 0, sizeof(cron_bitmap_t));

	bitmapfields[0] = NULL;
	bitmapfields[1] = bitmap->second;
	bitmapfields[2] = bitmap->minute;
	bitmapfields[3] = bitmap->hour;
	bitmapfields[4] = bitmap->day;
	bitmapfields[5] = bitmap->month;
	bitmapfields[6] = bitmap->weekday;

	while(1)
	{
		index = 0;
		while(*expression != 0 && *expression != ' ')
			field[index++] = *expression++;
		if(index == 0)
			break;
		expression++;
		field[index] = 0;
		if(!cron_prase_field(field, bitmapfields[fieldtype], fieldtype))
			return (-fieldtype);
		if(++fieldtype > CRON_FIELD_TYPE_WEEKDAY)
			break;
	}

	for(index = 0; index < sizeof(bitmap->weekday)/sizeof(bitmap->weekday[0]); index++)
	{
		if(bitmap->weekday[index] != 0)
		{
			weekused = 1;
			break;
		}
	}

	for(index = 0; index < sizeof(bitmap->day)/sizeof(bitmap->day[0]); index++)
	{
		if(bitmap->day[index] != 0)
		{
			daysused = 1;
			break;
		}
	}

	if(weekused == daysused)
		return (-(CRON_FIELD_TYPE_WEEKDAY + 1));

	return 0;
}

int cron_parse_future_timenodes_timestruct(const char* expression, const rl_time_t* current, rl_time_t* futures, uint32_t timenodes)
{
	cron_bitmap_t bitmap;
	rl_time_t t;
	uint32_t timestamp;
	if(cron_parse(expression, &bitmap) < 0)
		return -1;
	timestamp = rl_timeunpack(current);
    timestamp += 1; // 跳过当前时间
	while(timenodes != 0)
	{
		rl_timepack(timestamp, &t);
		if(bitmap.second[t.sec] && bitmap.minute[t.min] && bitmap.hour[t.hour] && bitmap.month[t.mon - 1] && (bitmap.day[t.mday - 1] || bitmap.weekday[t.wday]))
		{
			memcpy(futures, &t, sizeof(rl_time_t));
			futures++;
			timenodes--;
		}
		timestamp += 1;  /* 模拟时间经过 */
	}
	return 0;
}

int cron_parse_future_timenodes_timestamp(const char* expression, uint32_t current, uint32_t* futures, uint32_t timenodes)
{
	cron_bitmap_t bitmap;
	rl_time_t t;
	if(cron_parse(expression, &bitmap) < 0)
		return -1;
    current += 1; // 跳过当前时间
	while(timenodes != 0)
	{
		rl_timepack(current, &t);
		if(bitmap.second[t.sec] && bitmap.minute[t.min] && bitmap.hour[t.hour] && bitmap.month[t.mon - 1] && (bitmap.day[t.mday - 1] || bitmap.weekday[t.wday]))
		{
			*futures++ = rl_timeunpack(&t);
			timenodes--;
		}
		current += 1;  /* 模拟时间经过 */
	}
	return 0;
}





///////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// 使用示例
///
///

#if 0


#include <stdio.h>
#include <time.h>
#include "cron.h"

#define CRON_PARSE_STRUCT

#define debug_printf(...) do{printf(__VA_ARGS__);fflush(stdout);}while(0)

int main(void)
{
#ifdef CRON_PARSE_STRUCT
	rl_time_t current;
	rl_time_t futures[10];

	// 当前时间戳
	uint32_t timestamp = rl_time(NULL);

	// 转本地时间戳
	timestamp += (60 * 60 * 8);

	// 转为时间结构
	rl_timepack(timestamp, &current);

	/* 解析未来十次时间节点 */
	if(cron_parse_future_timenodes_timestruct("0 30 17 * * ?", &current, futures, 10) == 0)
	{
		for(int i = 0; i < 10; i++)
		{
			char buf[64];
			debug_printf("next timenode: %s \n", rl_timestring(&futures[i], buf, sizeof(buf)));
		}
	}
#else
	// 存储未来时间节点
	uint32_t futures[10];

	// 当前时间戳
	uint32_t timestamp = rl_time(NULL);

	// 转本地时间戳
	timestamp += (60 * 60 * 8);

	/* 解析未来十次时间节点 */
	if(cron_parse_future_timenodes_timestamp("0 30 17 * * ?", timestamp, futures, 10) == 0)
	{
		for(int i = 0; i < 10; i++)
		{
			char buf[64];
			rl_time_t node;
			rl_timepack(futures[i], &node);
			debug_printf("next timenode: %s \n", rl_timestring(&node, buf, sizeof(buf)));
		}
	}
#endif
	return 0;
}

#endif

